/**
 * Created by xiey on 2016/3/23.
 */
as.constant(
    'toasterConfig', {
        'limit'               : 0,                   // limits max number of toasts
        'tap-to-dismiss'      : true,
        'close-button'        : false,
        'close-html'          : '<button class="toast-close-button" type="button">&times;</button>',
        'newest-on-top'       : true,
        'time-out'            : 5000,
        'icon-classes'        : {
            error  : 'toast-error',
            info   : 'toast-info',
            wait   : 'toast-wait',
            success: 'toast-success',
            warn   : 'toast-warning'
        },
        defaultTitle          : {
            error  : '错误',
            info   : '提示',
            wait   : '请等待',
            success: '成功',
            warn   : '警告'
        },
        'body-output-type'    : '', // Options: '', 'trustedHtml', 'template', 'templateWithData', 'directive'
        'body-template'       : 'toasterBodyTmpl.html',
        'icon-class'          : 'toast-info',
        'position-class'      : 'toast-top-right', // Options (see CSS):
        // 'toast-top-full-width', 'toast-bottom-full-width', 'toast-center',
        // 'toast-top-left', 'toast-top-center', 'toast-top-right',
        // 'toast-bottom-left', 'toast-bottom-center', 'toast-bottom-right',
        'title-class'         : 'toast-title',
        'message-class'       : 'toast-message',
        'prevent-duplicates'  : false,
        'mouseover-timer-stop': true // stop timeout on mouseover and restart timer on mouseout
    }
).service(
    '$toast', [
        '$rootScope', 'toasterConfig', '$compile', function ($rootScope, toasterConfig, $compile) {
            this.pop = function (type, body, title, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) {
                // toaster 需要在页面中有 toaster-container
                if (!document.querySelector('#toast-container')) {
                    var scope = $rootScope.$new(true);
                    var $taostContainer = $compile('<toaster-container></toaster-container>')(scope);
                    document.body.appendChild($taostContainer[0]);
                }
                if (angular.isObject(type)) {
                    var params = type; // Enable named parameters as pop argument
                    this.toast = {
                        type           : params.type,
                        title          : params.title,
                        body           : params.body,
                        timeout        : params.timeout,
                        bodyOutputType : params.bodyOutputType,
                        clickHandler   : params.clickHandler,
                        showCloseButton: params.showCloseButton,
                        closeHtml      : params.closeHtml,
                        uid            : params.toastId,
                        onShowCallback : params.onShowCallback,
                        onHideCallback : params.onHideCallback,
                        directiveData  : params.directiveData
                    };
                    toastId = params.toastId;
                    toasterId = params.toasterId;
                } else {
                    this.toast = {
                        type           : type,
                        title          : title,
                        body           : body,
                        timeout        : timeout,
                        bodyOutputType : bodyOutputType,
                        clickHandler   : clickHandler,
                        showCloseButton: showCloseButton,
                        uid            : toastId,
                        onHideCallback : onHideCallback
                    };
                }
                $rootScope.$emit('toaster-newToast', toasterId, toastId);
            };

            this.clear = function (toasterId, toastId) {
                $rootScope.$emit('toaster-clearToasts', toasterId, toastId);
            };

            // Create one method per icon class, to allow to call toaster.info() and similar
            for (var type in toasterConfig['icon-classes']) {
                this[type] = createTypeMethod(type);
            }

            function createTypeMethod(toasterType) {
                return function (body, title, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) {
                    title = title || toasterConfig.defaultTitle[toasterType];
                    if (!angular.isObject(body)) {
                        this.pop(
                            toasterType,
                            body,
                            title,
                            timeout,
                            bodyOutputType,
                            clickHandler,
                            toasterId,
                            showCloseButton,
                            toastId,
                            onHideCallback);
                    } else { // 'title' is actually an object with options
                        this.pop(angular.extend(body, {type: toasterType}));
                    }
                };
            }
        }]
).factory(
    'toasterEventRegistry', [
        '$rootScope', function ($rootScope) {
            var deregisterNewToast = null, deregisterClearToasts = null, newToastEventSubscribers = [], clearToastsEventSubscribers = [], toasterFactory;

            toasterFactory = {
                setup: function () {
                    if (!deregisterNewToast) {
                        deregisterNewToast = $rootScope.$on(
                            'toaster-newToast', function (event, toasterId, toastId) {
                                for (var i = 0, len = newToastEventSubscribers.length; i < len; i++) {
                                    newToastEventSubscribers[i](event, toasterId, toastId);
                                }
                            });
                    }

                    if (!deregisterClearToasts) {
                        deregisterClearToasts = $rootScope.$on(
                            'toaster-clearToasts', function (event, toasterId, toastId) {
                                for (var i = 0, len = clearToastsEventSubscribers.length; i < len; i++) {
                                    clearToastsEventSubscribers[i](event, toasterId, toastId);
                                }
                            });
                    }
                },

                subscribeToNewToastEvent     : function (onNewToast) {
                    newToastEventSubscribers.push(onNewToast);
                },
                subscribeToClearToastsEvent  : function (onClearToasts) {
                    clearToastsEventSubscribers.push(onClearToasts);
                },
                unsubscribeToNewToastEvent   : function (onNewToast) {
                    var index = newToastEventSubscribers.indexOf(onNewToast);
                    if (index >= 0) {
                        newToastEventSubscribers.splice(index, 1);
                    }

                    if (newToastEventSubscribers.length === 0) {
                        deregisterNewToast();
                        deregisterNewToast = null;
                    }
                },
                unsubscribeToClearToastsEvent: function (onClearToasts) {
                    var index = clearToastsEventSubscribers.indexOf(onClearToasts);
                    if (index >= 0) {
                        clearToastsEventSubscribers.splice(index, 1);
                    }

                    if (clearToastsEventSubscribers.length === 0) {
                        deregisterClearToasts();
                        deregisterClearToasts = null;
                    }
                }
            };
            return {
                setup                        : toasterFactory.setup,
                subscribeToNewToastEvent     : toasterFactory.subscribeToNewToastEvent,
                subscribeToClearToastsEvent  : toasterFactory.subscribeToClearToastsEvent,
                unsubscribeToNewToastEvent   : toasterFactory.unsubscribeToNewToastEvent,
                unsubscribeToClearToastsEvent: toasterFactory.unsubscribeToClearToastsEvent
            };
        }]
    )
    .directive('directiveTemplate', ['$compile', '$injector', function ($compile, $injector) {
        return {
            restrict: 'A',
            scope   : {
                directiveName: '@directiveName',
                directiveData: '@directiveData'
            },
            replace : true,
            link    : function (scope, elm, attrs) {
                scope.$watch('directiveName', function (directiveName) {
                    if (angular.isUndefined(directiveName) || directiveName.length <= 0)
                        throw new Error('A valid directive name must be provided via the toast body argument when using bodyOutputType: directive');

                    var directive;

                    try {
                        directive = $injector.get(attrs.$normalize(directiveName) + 'Directive');
                    } catch (e) {
                        throw new Error(directiveName + ' could not be found. ' +
                            'The name should appear as it exists in the markup, not camelCased as it would appear in the directive declaration,' +
                            ' e.g. directive-name not directiveName.');
                    }


                    var directiveDetails = directive[0];

                    if (directiveDetails.scope !== true && directiveDetails.scope) {
                        throw new Error('Cannot use a directive with an isolated scope. ' +
                            'The scope must be either true or falsy (e.g. false/null/undefined). ' +
                            'Occurred for directive ' + directiveName + '.');
                    }

                    if (directiveDetails.restrict.indexOf('A') < 0) {
                        throw new Error('Directives must be usable as attributes. ' +
                            'Add "A" to the restrict option (or remove the option entirely). Occurred for directive ' +
                            directiveName + '.');
                    }

                    if (scope.directiveData)
                        scope.directiveData = angular.fromJson(scope.directiveData);

                    var template = $compile('<div ' + directiveName + '></div>')(scope);

                    elm.append(template);
                });
            }
        };
    }])
    .directive(
        'toasterContainer', [
            '$parse', '$rootScope', '$interval', '$sce', 'toasterConfig', '$toast', 'toasterEventRegistry',
            function ($parse, $rootScope, $interval, $sce, toasterConfig, toaster, toasterEventRegistry) {
                return {
                    replace   : true,
                    restrict  : 'EA',
                    scope     : true, // creates an internal scope for this directive (one per directive instance)
                    link      : function (scope, elm, attrs) {
                        var id = 0, mergedConfig;

                        // Merges configuration set in directive with default one
                        mergedConfig = angular.extend({}, toasterConfig, scope.$eval(attrs.toasterOptions));

                        scope.config = {
                            toasterId     : mergedConfig['toaster-id'],
                            position      : mergedConfig['position-class'],
                            title         : mergedConfig['title-class'],
                            message       : mergedConfig['message-class'],
                            tap           : mergedConfig['tap-to-dismiss'],
                            closeButton   : mergedConfig['close-button'],
                            closeHtml     : mergedConfig['close-html'],
                            animation     : mergedConfig['animation-class'],
                            mouseoverTimer: mergedConfig['mouseover-timer-stop']
                        };

                        scope.$on(
                            "$destroy", function () {
                                toasterEventRegistry.unsubscribeToNewToastEvent(scope._onNewToast);
                                toasterEventRegistry.unsubscribeToClearToastsEvent(scope._onClearToasts);
                            }
                        );

                        function setTimeout(toast, time) {
                            toast.timeoutPromise = $interval(
                                function () {
                                    scope.removeToast(toast.id);
                                }, time, 1
                            );
                        }

                        scope.configureTimer = function (toast) {
                            var timeout = angular.isNumber(toast.timeout) ? toast.timeout : mergedConfig['time-out'];
                            if (typeof timeout === "object") timeout = timeout[toast.type];
                            if (timeout > 0) {
                                setTimeout(toast, timeout);
                            }
                        };

                        function addToast(toast, toastId) {
                            toast.type = mergedConfig['icon-classes'][toast.type];
                            if (!toast.type) {
                                toast.type = mergedConfig['icon-class'];
                            }

                            if (mergedConfig['prevent-duplicates'] === true) {
                                // Prevent adding duplicate toasts if it's set
                                if (isUndefinedOrNull(toastId)) {
                                    if (scope.toasters.length > 0 && scope.toasters[scope.toasters.length - 1].body === toast.body) {
                                        return;
                                    }
                                } else {
                                    var i, len;
                                    for (i = 0, len = scope.toasters.length; i < len; i++) {
                                        if (scope.toasters[i].uid === toastId) {
                                            removeToast(i);
                                            // update loop
                                            i--;
                                            len = scope.toasters.length;
                                        }
                                    }
                                }
                            }

                            toast.id = ++id;
                            // Sure uid defined
                            if (!isUndefinedOrNull(toastId)) {
                                toast.uid = toastId;
                            }

                            // set the showCloseButton property on the toast so that
                            // each template can bind directly to the property to show/hide
                            // the close button
                            var closeButton = mergedConfig['close-button'];

                            // if toast.showCloseButton is a boolean value,
                            // it was specifically overriden in the pop arguments
                            if (typeof toast.showCloseButton === "boolean") {

                            } else if (typeof closeButton === "boolean") {
                                toast.showCloseButton = closeButton;
                            } else if (typeof closeButton === "object") {
                                var closeButtonForType = closeButton[toast.type];

                                if (typeof closeButtonForType !== "undefined" && closeButtonForType !== null) {
                                    toast.showCloseButton = closeButtonForType;
                                }
                            } else {
                                // if an option was not set, default to false.
                                toast.showCloseButton = false;
                            }

                            if (toast.showCloseButton) {
                                toast.closeHtml = $sce.trustAsHtml(toast.closeHtml || scope.config.closeHtml);
                            }

                            // Set the toast.bodyOutputType to the default if it isn't set
                            toast.bodyOutputType = toast.bodyOutputType || mergedConfig['body-output-type'];
                            switch (toast.bodyOutputType) {
                                case 'trustedHtml':
                                    toast.html = $sce.trustAsHtml(toast.body);
                                    break;
                                case 'template':
                                    toast.bodyTemplate = toast.body || mergedConfig['body-template'];
                                    break;
                                case 'templateWithData':
                                    var fcGet = $parse(toast.body || mergedConfig['body-template']);
                                    var templateWithData = fcGet(scope);
                                    toast.bodyTemplate = templateWithData.template;
                                    toast.data = templateWithData.data;
                                    break;
                                case 'directive':
                                    toast.html = toast.body;
                                    break;
                            }

                            scope.configureTimer(toast);

                            if (mergedConfig['newest-on-top'] === true) {
                                scope.toasters.unshift(toast);
                                if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) {
                                    scope.toasters.pop();
                                }
                            } else {
                                scope.toasters.push(toast);
                                if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) {
                                    scope.toasters.shift();
                                }
                            }

                            if (angular.isFunction(toast.onShowCallback)) {
                                toast.onShowCallback();
                            }
                        }

                        scope.removeToast = function (id) {
                            var i, len;
                            for (i = 0, len = scope.toasters.length; i < len; i++) {
                                if (scope.toasters[i].id === id) {
                                    removeToast(i);
                                    break;
                                }
                            }
                        };

                        function removeToast(toastIndex) {
                            var toast = scope.toasters[toastIndex];

                            // toast is always defined since the index always has a match
                            if (toast.timeoutPromise) {
                                $interval.cancel(toast.timeoutPromise);
                            }
                            scope.toasters.splice(toastIndex, 1);

                            if (angular.isFunction(toast.onHideCallback)) {
                                toast.onHideCallback();
                            }
                        }

                        function removeAllToasts(toastId) {
                            for (var i = scope.toasters.length - 1; i >= 0; i--) {
                                if (isUndefinedOrNull(toastId)) {
                                    removeToast(i);
                                } else {
                                    if (scope.toasters[i].uid == toastId) {
                                        removeToast(i);
                                    }
                                }
                            }
                        }

                        scope.toasters = [];

                        function isUndefinedOrNull(val) {
                            return angular.isUndefined(val) || val === null;
                        }

                        scope._onNewToast = function (event, toasterId, toastId) {
                            // Compatibility: if toaster has no toasterId defined, and if call to display
                            // hasn't either, then the request is for us

                            if ((isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) {
                                addToast(toaster.toast, toastId);
                            }
                        };
                        scope._onClearToasts = function (event, toasterId, toastId) {
                            // Compatibility: if toaster has no toasterId defined, and if call to display
                            // hasn't either, then the request is for us
                            if (toasterId == '*' || (isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) {
                                removeAllToasts(toastId);
                            }
                        };

                        toasterEventRegistry.setup();

                        toasterEventRegistry.subscribeToNewToastEvent(scope._onNewToast);
                        toasterEventRegistry.subscribeToClearToastsEvent(scope._onClearToasts);
                    },
                    controller: [
                        '$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
                            // Called on mouseover
                            $scope.stopTimer = function (toast) {
                                if ($scope.config.mouseoverTimer === true) {
                                    if (toast.timeoutPromise) {
                                        $interval.cancel(toast.timeoutPromise);
                                        toast.timeoutPromise = null;
                                    }
                                }
                            };

                            // Called on mouseout
                            $scope.restartTimer = function (toast) {
                                if ($scope.config.mouseoverTimer === true) {
                                    if (!toast.timeoutPromise) {
                                        $scope.configureTimer(toast);
                                    }
                                } else if (toast.timeoutPromise === null) {
                                    $scope.removeToast(toast.id);
                                }
                            };

                            $scope.click = function (toast, isCloseButton) {
                                if ($scope.config.tap === true || (toast.showCloseButton === true && isCloseButton === true)) {
                                    var removeToast = true;
                                    if (toast.clickHandler) {
                                        if (angular.isFunction(toast.clickHandler)) {
                                            removeToast = toast.clickHandler(toast, isCloseButton);
                                        } else if (angular.isFunction($scope.$parent.$eval(toast.clickHandler))) {
                                            removeToast = $scope.$parent.$eval(toast.clickHandler)(toast, isCloseButton);
                                        } else {
                                            console.log("TOAST-NOTE: Your click handler is not inside a parent scope of toaster-container.");
                                        }
                                    }
                                    if (removeToast) {
                                        $scope.removeToast(toast.id);
                                    }
                                }
                            };
                        }],
                    template  : function () {
                        return '<div id="toast-container" ng-class="[config.position, config.animation]">' +
                            '<div ng-repeat="toaster in toasters" class="toast" ng-class="toaster.type" ng-click="click(toaster)" ng-mouseover="stopTimer(toaster)" ng-mouseout="restartTimer(toaster)">' +
                            '<div ng-if="toaster.showCloseButton" ng-click="click(toaster, true)" ng-bind-html="toaster.closeHtml"></div>' +
                            '<div class="icon" ng-switch="toaster.type">' +
                            '<svg ng-switch-when="toast-wait" class="loading" viewBox="0 0 16 16"><path d="M16 8c-0.020-1.045-0.247-2.086-0.665-3.038-0.417-0.953-1.023-1.817-1.766-2.53s-1.624-1.278-2.578-1.651c-0.953-0.374-1.978-0.552-2.991-0.531-1.013 0.020-2.021 0.24-2.943 0.646-0.923 0.405-1.758 0.992-2.449 1.712s-1.237 1.574-1.597 2.497c-0.361 0.923-0.533 1.914-0.512 2.895 0.020 0.981 0.234 1.955 0.627 2.847 0.392 0.892 0.961 1.7 1.658 2.368s1.523 1.195 2.416 1.543c0.892 0.348 1.851 0.514 2.799 0.493 0.949-0.020 1.89-0.227 2.751-0.608 0.862-0.379 1.642-0.929 2.287-1.604s1.154-1.472 1.488-2.335c0.204-0.523 0.342-1.069 0.415-1.622 0.019 0.001 0.039 0.002 0.059 0.002 0.552 0 1-0.448 1-1 0-0.028-0.001-0.056-0.004-0.083h0.004zM14.411 10.655c-0.367 0.831-0.898 1.584-1.55 2.206s-1.422 1.112-2.254 1.434c-0.832 0.323-1.723 0.476-2.608 0.454-0.884-0.020-1.759-0.215-2.56-0.57-0.801-0.354-1.526-0.867-2.125-1.495s-1.071-1.371-1.38-2.173c-0.31-0.801-0.457-1.66-0.435-2.512s0.208-1.694 0.551-2.464c0.342-0.77 0.836-1.468 1.441-2.044s1.321-1.029 2.092-1.326c0.771-0.298 1.596-0.438 2.416-0.416s1.629 0.202 2.368 0.532c0.74 0.329 1.41 0.805 1.963 1.387s0.988 1.27 1.272 2.011c0.285 0.74 0.418 1.532 0.397 2.32h0.004c-0.002 0.027-0.004 0.055-0.004 0.083 0 0.516 0.39 0.94 0.892 0.994-0.097 0.544-0.258 1.075-0.481 1.578z"></path></svg>' +
                            '<svg ng-switch-when="toast-error" viewBox="0 0 16 16"><path d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM8 14.5c-3.59 0-6.5-2.91-6.5-6.5s2.91-6.5 6.5-6.5 6.5 2.91 6.5 6.5-2.91 6.5-6.5 6.5z"></path><path d="M10.5 4l-2.5 2.5-2.5-2.5-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 2.5-2.5 2.5 2.5 1.5-1.5-2.5-2.5 2.5-2.5z"></path></svg>' +
                            '<svg ng-switch-when="toast-success" viewBox="0 0 16 16"><path d="M13.5 2l-7.5 7.5-3.5-3.5-2.5 2.5 6 6 10-10z"></path></svg>' +
                            '<svg ng-switch-when="toast-warning" viewBox="0 0 16 16"><path d="M8 1.45l6.705 13.363h-13.409l6.705-13.363zM8 0c-0.345 0-0.69 0.233-0.951 0.698l-6.829 13.611c-0.523 0.93-0.078 1.691 0.989 1.691h13.583c1.067 0 1.512-0.761 0.989-1.691h0l-6.829-13.611c-0.262-0.465-0.606-0.698-0.951-0.698v0z"></path><path d="M9 13c0 0.552-0.448 1-1 1s-1-0.448-1-1c0-0.552 0.448-1 1-1s1 0.448 1 1z"></path><path d="M8 11c-0.552 0-1-0.448-1-1v-3c0-0.552 0.448-1 1-1s1 0.448 1 1v3c0 0.552-0.448 1-1 1z"></path></svg>' +
                            '<svg ng-switch-default viewBox="0 0 16 16"><path d="M7 4.75c0-0.412 0.338-0.75 0.75-0.75h0.5c0.412 0 0.75 0.338 0.75 0.75v0.5c0 0.412-0.338 0.75-0.75 0.75h-0.5c-0.412 0-0.75-0.338-0.75-0.75v-0.5z"></path><path d="M10 12h-4v-1h1v-3h-1v-1h3v4h1z"></path><path d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM8 14.5c-3.59 0-6.5-2.91-6.5-6.5s2.91-6.5 6.5-6.5 6.5 2.91 6.5 6.5-2.91 6.5-6.5 6.5z"></path></svg>' +
                            '</div>' +
                            '<div ng-class="config.title">{{toaster.title}}</div>' +
                            '<div ng-class="config.message" ng-switch on="toaster.bodyOutputType">' +
                            '<div ng-switch-when="trustedHtml" ng-bind-html="toaster.html"></div>' +
                            '<div ng-switch-when="template"><div ng-include="toaster.bodyTemplate"></div></div>' +
                            '<div ng-switch-when="templateWithData"><div ng-include="toaster.bodyTemplate"></div></div>' +
                            '<div ng-switch-when="directive"><div directive-template directive-name="{{toaster.html}}" directive-data="{{toaster.directiveData}}"></div></div>' +
                            '<div ng-switch-default >{{toaster.body}}</div>' +
                            '</div>' +
                            '</div>' +
                            '</div>';
                    }
                };
            }]
    );